#include "sqplus.h"
#include <stdio.h>

namespace SqPlus {

  static SQInteger getVarInfo( StackHandler & sa, VarRefPtr & vr ) {
    HSQOBJECT htable = sa.GetObjectHandle( 1 );
    SquirrelObject table( htable );
    #ifdef _DEBUG
    SQObjectType type = ( SQObjectType )sa.GetType( 2 );
    #endif
    const SQChar * el = sa.GetString( 2 );
    ScriptStringVar256 varNameTag;
    getVarNameTag( varNameTag, sizeof( varNameTag ), el );
    SQUserPointer data = 0;
    if( !table.RawGetUserData( varNameTag, &data ) ) {
      return sa.ThrowError( sqT( "getVarInfo: Could not retrieve UserData" ) );
    }
    vr = ( VarRefPtr )data;
    return SQ_OK;
  }

  static SQInteger getInstanceVarInfo( StackHandler & sa, VarRefPtr & vr, SQUserPointer & data ) {
    HSQOBJECT ho = sa.GetObjectHandle( 1 );
    SquirrelObject instance( ho );
    #ifdef _DEBUG
    SQObjectType type = ( SQObjectType )sa.GetType( 2 );
    #endif
    const SQChar * el = sa.GetString( 2 );
    ScriptStringVar256 varNameTag;
    getVarNameTag( varNameTag, sizeof( varNameTag ), el );
    SQUserPointer ivrData = 0;
    if( !instance.RawGetUserData( varNameTag, &ivrData ) ) {
      return sa.ThrowError( sqT( "getInstanceVarInfo: Could not retrieve UserData" ) );
    }
    vr = ( VarRefPtr )ivrData;
    unsigned char * up;
    if( !( vr->access & ( VAR_ACCESS_STATIC | VAR_ACCESS_CONSTANT ) ) ) {
      #ifdef SQ_USE_CLASS_INHERITANCE
      SQUserPointer typetag;
      instance.GetTypeTag( &typetag );
      if( typetag != vr->instanceType ) {
        SquirrelObject typeTable = instance.GetValue( SQ_CLASS_OBJECT_TABLE_NAME );
        up = ( unsigned char * )typeTable.GetUserPointer( INT_T( ( size_t )vr->instanceType ) );
        if( !up ) {
          throw SquirrelError( sqT( "Invalid Instance Type" ) );
        }
      } else
      { up = ( unsigned char * )instance.GetInstanceUP( 0 ); }
      #else
      up = ( unsigned char * )instance.GetInstanceUP( 0 );
      #endif
      up += ( size_t )vr->offsetOrAddrOrConst;
    } else
    { up = ( unsigned char * )vr->offsetOrAddrOrConst; }
    data = up;
    return SQ_OK;
  }

  static SQInteger setVar( StackHandler & sa, VarRef * vr, void * data ) {
    if( vr->access & ( VAR_ACCESS_READ_ONLY | VAR_ACCESS_CONSTANT ) ) {
      ScriptStringVar256 msg;
      const SQChar * el = sa.GetString( 2 );
      SCSNPRINTF( msg.s, sizeof( msg.s ), sqT( "setVar(): Cannot write to constant: %s" ), el );
      throw SquirrelError( msg.s );
    }
    switch( vr->type ) {
      case TypeInfo<INT_T>::TypeID: {
        INT_T * val = ( INT_T * )data;
        if( val ) {
          *val = sa.GetInt( 3 );
          return sa.Return( *val );
        }
        break;
      }
      case TypeInfo<FLOAT_T>::TypeID: {
        FLOAT_T * val = ( FLOAT_T * )data;
        if( val ) {
          *val = sa.GetFloat( 3 );
          return sa.Return( *val );
        }
        break;
      }
      case TypeInfo<bool>::TypeID: {
        bool * val = ( bool * )data;
        if( val ) {
          *val = sa.GetBool( 3 ) ? true : false;
          return sa.Return( *val );
        }
        break;
      }
      case VAR_TYPE_INSTANCE: {
        SQUserPointer src = sa.GetInstanceUp( 3, ( SQUserPointer )vr->copyFunc );
        if( !src ) {
          throw SquirrelError( sqT( "INSTANCE type assignment mismatch" ) );
        }
        vr->copyFunc( data, src );
        return 0;
      }
      case TypeInfo<SQUserPointer>::TypeID: {
        ScriptStringVar256 msg;
        const SQChar * el = sa.GetString( 2 );
        SCSNPRINTF( msg.s, sizeof( msg.s ), sqT( "setVar(): Cannot write to an SQUserPointer: %s" ), el );
        throw SquirrelError( msg.s );
      }
      case TypeInfo<ScriptStringVarBase>::TypeID: {
        ScriptStringVarBase * val = ( ScriptStringVarBase * )data;
        if( val ) {
          const SQChar * strVal = sa.GetString( 3 );
          if( strVal ) {
            *val = strVal;
            return sa.Return( val->s );
          }
        }
        break;
      }
      default:
        break;
    }
    return SQ_ERROR;
  }

  static SQInteger getVar( StackHandler & sa, VarRef * vr, void * data ) {
    switch( vr->type ) {
      case TypeInfo<INT_T>::TypeID: {
        if( !( vr->access & VAR_ACCESS_CONSTANT ) ) {
          INT_T * val = ( INT_T * )data;
          if( val ) {
            return sa.Return( *val );
          }
        } else {
          INT_T * val = ( INT_T * )&data;
          return sa.Return( *val );
        }
        break;
      }
      case TypeInfo<FLOAT_T>::TypeID: {
        if( !( vr->access & VAR_ACCESS_CONSTANT ) ) {
          FLOAT_T * val = ( FLOAT_T * )data;
          if( val ) {
            return sa.Return( *val );
          }
        } else {
          FLOAT_T * val = ( FLOAT_T * )&data;
          return sa.Return( *val );
        }
        break;
      }
      case TypeInfo<bool>::TypeID: {
        if( !( vr->access & VAR_ACCESS_CONSTANT ) ) {
          bool * val = ( bool * )data;
          if( val ) {
            return sa.Return( *val );
          }
        } else {
          bool * val = ( bool * )&data;
          return sa.Return( *val );
        }
        break;
      }
      case VAR_TYPE_INSTANCE:
        if( !CreateNativeClassInstance( sa.GetVMPtr(), vr->typeName, data, 0 ) ) {
          ScriptStringVar256 msg;
          SCSNPRINTF( msg.s, sizeof( msg.s ), sqT( "getVar(): Could not create instance: %s" ), vr->typeName );
          throw SquirrelError( msg.s );
        }
        return 1;
      case TypeInfo<SQUserPointer>::TypeID: {
        return sa.Return( data );
      }
      case TypeInfo<ScriptStringVarBase>::TypeID: {
        if( !( vr->access & VAR_ACCESS_CONSTANT ) ) {
          ScriptStringVarBase * val = ( ScriptStringVarBase * )data;
          if( val ) {
            return sa.Return( val->s );
          }
        } else
        { throw SquirrelError( sqT( "getVar(): Invalid type+access: 'ScriptStringVarBase' with VAR_ACCESS_CONSTANT (use VAR_ACCESS_READ_ONLY instead)" ) ); }
        break;
      }
      case TypeInfo<const SQChar *>::TypeID: {
        if( !( vr->access & VAR_ACCESS_CONSTANT ) ) {
          throw SquirrelError( sqT( "getVar(): Invalid type+access: 'const SQChar *' without VAR_ACCESS_CONSTANT" ) );
        } else
        { return sa.Return( ( const SQChar * )data ); }
        break;
      }
      default:
        break;
    }
    return SQ_ERROR;
  }



  SQInteger setVarFunc( HSQUIRRELVM v ) {
    StackHandler sa( v );
    if( sa.GetType( 1 ) == OT_TABLE ) {
      VarRefPtr vr;
      SQInteger res = getVarInfo( sa, vr );
      if( res != SQ_OK ) {
        return res;
      }
      return setVar( sa, vr, vr->offsetOrAddrOrConst );
    }
    return SQ_ERROR;
  }

  SQInteger getVarFunc( HSQUIRRELVM v ) {
    StackHandler sa( v );
    if( sa.GetType( 1 ) == OT_TABLE ) {
      VarRefPtr vr;
      SQInteger res = getVarInfo( sa, vr );
      if( res != SQ_OK ) {
        return res;
      }
      return getVar( sa, vr, vr->offsetOrAddrOrConst );
    }
    return SQ_ERROR;
  }



  SQInteger setInstanceVarFunc( HSQUIRRELVM v ) {
    StackHandler sa( v );
    if( sa.GetType( 1 ) == OT_INSTANCE ) {
      VarRefPtr vr;
      void * data;
      SQInteger res = getInstanceVarInfo( sa, vr, data );
      if( res != SQ_OK ) {
        return res;
      }
      return setVar( sa, vr, data );
    }
    return SQ_ERROR;
  }

  SQInteger getInstanceVarFunc( HSQUIRRELVM v ) {
    StackHandler sa( v );
    if( sa.GetType( 1 ) == OT_INSTANCE ) {
      VarRefPtr vr;
      void * data;
      SQInteger res = getInstanceVarInfo( sa, vr, data );
      if( res != SQ_OK ) {
        return res;
      }
      return getVar( sa, vr, data );
    }
    return SQ_ERROR;
  }



  BOOL_T CreateClass( HSQUIRRELVM v, SquirrelObject & newClass, SQUserPointer classType, const SQChar * name, const SQChar * baseName ) {
    SQInteger oldtop = sq_gettop( v );
    sq_pushroottable( v );
    sq_pushstring( v, name, -1 );
    if( baseName ) {
      sq_pushstring( v, baseName, -1 );
      if( SQ_FAILED( sq_get( v, -3 ) ) ) {
        sq_settop( v, oldtop );
        return FALSE;
      }
    }
    if( SQ_FAILED( sq_newclass( v, baseName ? 1 : 0 ) ) ) {
      sq_settop( v, oldtop );
      return FALSE;
    }
    newClass.AttachToStackObject( -1 );
    sq_settypetag( v, -1, classType );
    sq_createslot( v, -3 );
    sq_pop( v, 1 );
    return TRUE;
  }

  SquirrelObject RegisterClassType( HSQUIRRELVM v, const SQChar * scriptClassName, SQUserPointer classType, SQFUNCTION constructor ) {
    SQInteger top = sq_gettop( v );
    SquirrelObject newClass;
    if( CreateClass( v, newClass, classType, scriptClassName ) ) {
      SquirrelVM::CreateFunction( newClass, constructor, sqT( "constructor" ) );
    }
    sq_settop( v, top );
    return newClass;
  }

};


